/*
  author Sylvain Bertrand <digital.ragnarok@gmail.com>
  Protected by GNU Affero GPL v3 with some exceptions.
  See README at root of alga tree.
*/

#include <linux/pci.h>
#include <asm/byteorder.h>
#include <linux/delay.h>
#include <linux/cdev.h>

#include "types.h"
#include "ba.h"
#include "ucode.h"
#include "cp.h"
#include "gpu.h"
#include "irq.h"
#include "drv.h"
#include "mc.h"
#include "regs.h"

/* stop dce silicium block accessing vram */
void mc_dce_stop(struct pci_dev *dev, struct mc_dce_save *save)
{
	struct dev_drv_data *drv_data;
	drv_data = pci_get_drvdata(dev);

	save->vga_ctl[0] = rr32(dev, D0VGA_CTL);
	save->vga_ctl[1] = rr32(dev, D1VGA_CTL);
	if (drv_data->crtcs_n >= 4) {
		save->vga_ctl[2] = rr32(dev, D2VGA_CTL);
		save->vga_ctl[3] = rr32(dev, D3VGA_CTL);
		if (drv_data->crtcs_n >= 6) {
			save->vga_ctl[4] = rr32(dev, D4VGA_CTL);
			save->vga_ctl[5] = rr32(dev, D5VGA_CTL);
		}
	}

	save->vga_render_ctl = rr32(dev, VGA_RENDER_CTL);
	save->vga_hdp_ctl = rr32(dev, VGA_HDP_CTL);

	save->crtc_ctl[0] = rr32(dev, CRTC_CTL + CRTC0_REG_OFFSET);
	save->crtc_ctl[1] = rr32(dev, CRTC_CTL + CRTC1_REG_OFFSET);
	if (drv_data->crtcs_n >= 4) {
		save->crtc_ctl[2] = rr32(dev, CRTC_CTL + CRTC2_REG_OFFSET);
		save->crtc_ctl[3] = rr32(dev, CRTC_CTL + CRTC3_REG_OFFSET);
		if (drv_data->crtcs_n >= 6) {
			save->crtc_ctl[4] = rr32(dev, CRTC_CTL
							+ CRTC4_REG_OFFSET);
			save->crtc_ctl[5] = rr32(dev, CRTC_CTL
							+ CRTC5_REG_OFFSET);
		}
	}

	/* stop all video */
	wr32(dev, 0, VGA_RENDER_CTL);
	wr32(dev, 1, CRTC_UPDATE_LOCK + CRTC0_REG_OFFSET);
	wr32(dev, 1, CRTC_UPDATE_LOCK + CRTC1_REG_OFFSET);
	if (drv_data->crtcs_n >= 4) {
		wr32(dev, 1, CRTC_UPDATE_LOCK + CRTC2_REG_OFFSET);
		wr32(dev, 1, CRTC_UPDATE_LOCK + CRTC3_REG_OFFSET);
		if (drv_data->crtcs_n >= 6) {
			wr32(dev, 1, CRTC_UPDATE_LOCK + CRTC4_REG_OFFSET);
			wr32(dev, 1, CRTC_UPDATE_LOCK + CRTC5_REG_OFFSET);
		}
	}

	wr32(dev, 0, CRTC_CTL + CRTC0_REG_OFFSET);
	wr32(dev, 0, CRTC_CTL + CRTC1_REG_OFFSET);
	if (drv_data->crtcs_n >= 4) {
		wr32(dev, 0, CRTC_CTL + CRTC2_REG_OFFSET);
		wr32(dev, 0, CRTC_CTL + CRTC3_REG_OFFSET);
		if (drv_data->crtcs_n >= 6) {
			wr32(dev, 0, CRTC_CTL + CRTC4_REG_OFFSET);
			wr32(dev, 0, CRTC_CTL + CRTC5_REG_OFFSET);
		}
	}

	wr32(dev, 0, CRTC_UPDATE_LOCK + CRTC0_REG_OFFSET);
	wr32(dev, 0, CRTC_UPDATE_LOCK + CRTC1_REG_OFFSET);
	if (drv_data->crtcs_n >= 4) {
		wr32(dev, 0, CRTC_UPDATE_LOCK + CRTC2_REG_OFFSET);
		wr32(dev, 0, CRTC_UPDATE_LOCK + CRTC3_REG_OFFSET);
		if (drv_data->crtcs_n >= 6) {
			wr32(dev, 0, CRTC_UPDATE_LOCK + CRTC4_REG_OFFSET);
			wr32(dev, 0, CRTC_UPDATE_LOCK + CRTC5_REG_OFFSET);
		}
	}

	wr32(dev, 0, D0VGA_CTL);
	wr32(dev, 0, D1VGA_CTL);
	if (drv_data->crtcs_n >= 4) {
		wr32(dev, 0, D2VGA_CTL);
		wr32(dev, 0, D3VGA_CTL);
		if (drv_data->crtcs_n >= 6) {
			wr32(dev, 0, D4VGA_CTL);
			wr32(dev, 0, D5VGA_CTL);
		}
	}
}

/* restart dce silcium block accessing vram */
void mc_dce_resume(struct pci_dev *dev, struct mc_dce_save *save)
{
	struct dev_drv_data *drv_data;
	drv_data = pci_get_drvdata(dev);

	wr32(dev, upper_32_bits(drv_data->vram.range.start),
			GRPH_PRIMARY_SURFACE_ADDR_HIGH + CRTC0_REG_OFFSET);
	wr32(dev, upper_32_bits(drv_data->vram.range.start),
			GRPH_SECONDARY_SURFACE_ADDR_HIGH + CRTC0_REG_OFFSET);
	wr32(dev, lower_32_bits(drv_data->vram.range.start),
				GRPH_PRIMARY_SURFACE_ADDR + CRTC0_REG_OFFSET);
	wr32(dev, lower_32_bits(drv_data->vram.range.start),
				GRPH_SECONDARY_SURFACE_ADDR + CRTC0_REG_OFFSET);
	wr32(dev, upper_32_bits(drv_data->vram.range.start),
			GRPH_PRIMARY_SURFACE_ADDR_HIGH + CRTC1_REG_OFFSET);
	wr32(dev, upper_32_bits(drv_data->vram.range.start),
			GRPH_SECONDARY_SURFACE_ADDR_HIGH + CRTC1_REG_OFFSET);
	wr32(dev, lower_32_bits(drv_data->vram.range.start),
				GRPH_PRIMARY_SURFACE_ADDR + CRTC1_REG_OFFSET);
	wr32(dev, lower_32_bits(drv_data->vram.range.start),
				GRPH_SECONDARY_SURFACE_ADDR + CRTC1_REG_OFFSET);

	if (drv_data->crtcs_n >= 4) {
		wr32(dev, upper_32_bits(drv_data->vram.range.start),
			GRPH_PRIMARY_SURFACE_ADDR_HIGH + CRTC2_REG_OFFSET);
		wr32(dev, upper_32_bits(drv_data->vram.range.start),
			GRPH_SECONDARY_SURFACE_ADDR_HIGH + CRTC2_REG_OFFSET);
		wr32(dev, lower_32_bits(drv_data->vram.range.start),
				GRPH_PRIMARY_SURFACE_ADDR + CRTC2_REG_OFFSET);
		wr32(dev, lower_32_bits(drv_data->vram.range.start),
				GRPH_SECONDARY_SURFACE_ADDR + CRTC2_REG_OFFSET);
		wr32(dev, upper_32_bits(drv_data->vram.range.start),
			GRPH_PRIMARY_SURFACE_ADDR_HIGH + CRTC3_REG_OFFSET);
		wr32(dev, upper_32_bits(drv_data->vram.range.start),
			GRPH_SECONDARY_SURFACE_ADDR_HIGH + CRTC3_REG_OFFSET);
		wr32(dev, lower_32_bits(drv_data->vram.range.start),
				GRPH_PRIMARY_SURFACE_ADDR + CRTC3_REG_OFFSET);
		wr32(dev, lower_32_bits(drv_data->vram.range.start),
				GRPH_SECONDARY_SURFACE_ADDR + CRTC3_REG_OFFSET);
		if (drv_data->crtcs_n >= 6) {
			wr32(dev, upper_32_bits(drv_data->vram.range.start),
						GRPH_PRIMARY_SURFACE_ADDR_HIGH
							+ CRTC4_REG_OFFSET);
			wr32(dev, upper_32_bits(drv_data->vram.range.start),
						GRPH_SECONDARY_SURFACE_ADDR_HIGH
							+ CRTC4_REG_OFFSET);
			wr32(dev, lower_32_bits(drv_data->vram.range.start),
				GRPH_PRIMARY_SURFACE_ADDR + CRTC4_REG_OFFSET);
			wr32(dev, lower_32_bits(drv_data->vram.range.start),
				GRPH_SECONDARY_SURFACE_ADDR + CRTC4_REG_OFFSET);
			wr32(dev, upper_32_bits(drv_data->vram.range.start),
						GRPH_PRIMARY_SURFACE_ADDR_HIGH
							+ CRTC5_REG_OFFSET);
			wr32(dev, upper_32_bits(drv_data->vram.range.start),
						GRPH_SECONDARY_SURFACE_ADDR_HIGH
							+ CRTC5_REG_OFFSET);
			wr32(dev, lower_32_bits(drv_data->vram.range.start),
				GRPH_PRIMARY_SURFACE_ADDR + CRTC5_REG_OFFSET);
			wr32(dev, lower_32_bits(drv_data->vram.range.start),
				GRPH_SECONDARY_SURFACE_ADDR + CRTC5_REG_OFFSET);
		}
	}

	wr32(dev, upper_32_bits(drv_data->vram.range.start),
							VGA_MEM_BASE_ADDR_HIGH);
	wr32(dev, lower_32_bits(drv_data->vram.range.start), VGA_MEM_BASE_ADDR);

	/* unlock host access */
	wr32(dev, save->vga_hdp_ctl, VGA_HDP_CTL );
	mdelay(1);

	/* restore video state */
	wr32(dev, save->vga_ctl[0], D0VGA_CTL);
	wr32(dev, save->vga_ctl[1], D1VGA_CTL);
	if (drv_data->crtcs_n >= 4) {
		wr32(dev, save->vga_ctl[2], D2VGA_CTL);
		wr32(dev, save->vga_ctl[3], D3VGA_CTL);
		if (drv_data->crtcs_n >= 6) {
			wr32(dev, save->vga_ctl[4], D4VGA_CTL);
			wr32(dev, save->vga_ctl[5], D5VGA_CTL);
		}
	}

	wr32(dev, 1, CRTC_UPDATE_LOCK + CRTC0_REG_OFFSET);
	wr32(dev, 1, CRTC_UPDATE_LOCK + CRTC1_REG_OFFSET);
	if (drv_data->crtcs_n >= 4) {
		wr32(dev, 1, CRTC_UPDATE_LOCK + CRTC2_REG_OFFSET);
		wr32(dev, 1, CRTC_UPDATE_LOCK + CRTC3_REG_OFFSET);
		if (drv_data->crtcs_n >= 6) {
			wr32(dev, 1, CRTC_UPDATE_LOCK + CRTC4_REG_OFFSET);
			wr32(dev, 1, CRTC_UPDATE_LOCK + CRTC5_REG_OFFSET);
		}
	}

	wr32(dev, save->crtc_ctl[0], CRTC_CTL + CRTC0_REG_OFFSET);
	wr32(dev, save->crtc_ctl[1], CRTC_CTL + CRTC1_REG_OFFSET);
	if (drv_data->crtcs_n >= 4) {
		wr32(dev, save->crtc_ctl[2], CRTC_CTL + CRTC2_REG_OFFSET);
		wr32(dev, save->crtc_ctl[3], CRTC_CTL + CRTC3_REG_OFFSET);
		if (drv_data->crtcs_n >= 6) {
			wr32(dev, save->crtc_ctl[4], CRTC_CTL
							+ CRTC4_REG_OFFSET);
			wr32(dev, save->crtc_ctl[5], CRTC_CTL
							+ CRTC5_REG_OFFSET);
		}
	}

	wr32(dev, 0, CRTC_UPDATE_LOCK + CRTC0_REG_OFFSET);
	wr32(dev, 0, CRTC_UPDATE_LOCK + CRTC1_REG_OFFSET);
	if (drv_data->crtcs_n >= 4) {
		wr32(dev, 0, CRTC_UPDATE_LOCK + CRTC2_REG_OFFSET);
		wr32(dev, 0, CRTC_UPDATE_LOCK + CRTC3_REG_OFFSET);
		if (drv_data->crtcs_n >= 6) {
			wr32(dev, 0, CRTC_UPDATE_LOCK + CRTC4_REG_OFFSET);
			wr32(dev, 0, CRTC_UPDATE_LOCK + CRTC5_REG_OFFSET);
		}
	}
	wr32(dev, save->vga_render_ctl, VGA_RENDER_CTL);
}

int mc_wait_for_idle(struct pci_dev *dev)
{
	unsigned i;

	/* 100000 us = 100 ms */	
	for (i = 0; i < 100000; ++i) {
		if (!(rr32(dev, SRBM_STATUS) & MC_STATUS_MASK))
			return 0;
		udelay(1);
	}
	return -1;
}

int mc_program(struct pci_dev *dev)
{
	struct dev_drv_data *drv_data;
	int i;
	int j;
	struct mc_dce_save mc_dce_save;
	u32 tmp;

	drv_data = pci_get_drvdata(dev);

	if (!IS_GPU_PAGE_ALIGNED(drv_data->vram.range.start)) {
		dev_err(&dev->dev, "vram start is not aligned on gpu page\n");
		return -EINVAL;
	}

	/* initialize hdp */
	for (i = 0, j = 0; i < 32; i++, j += 0x18) {
		wr32(dev, 0x00000000, 0x2c14 + j);
		wr32(dev, 0x00000000, 0x2c18 + j);
		wr32(dev, 0x00000000, 0x2c1c + j);
		wr32(dev, 0x00000000, 0x2c20 + j);
		wr32(dev, 0x00000000, 0x2c24 + j);
	}
	wr32(dev, 0, HDP_REG_COHERENCY_FLUSH_CTL);

	mc_dce_stop(dev, &mc_dce_save);
	if (mc_wait_for_idle(dev))
		dev_warn(&dev->dev, "mc_program:stop:"
					" wait for mc idle timed out\n");

	wr32(dev, VGA_MEM_DISABLE, VGA_HDP_CTL);

	wr32(dev, GPU_PAGE_IDX(drv_data->vram.range.start),
						MC_VM_SYS_APERTURE_LOW_ADDR);
	wr32(dev, GPU_PAGE_IDX(drv_data->vram.range.end),
						MC_VM_SYS_APERTURE_HIGH_ADDR);
	/* scratch gpu page right after vga range */
	wr32(dev, GPU_PAGE_IDX(VGA_RANGE_SZ), MC_VM_SYS_APERTURE_DEFAULT_ADDR);

	/*
	 * MC_VRAM_LOCATION:
	 *   [15:8] last gpu address 16MB block index 
	 *   [7:0]  first gpu address 16MB block index
	 */
	tmp = ((drv_data->vram.range.end >> 24) & 0xffff) << 16;
	tmp |= ((drv_data->vram.range.start >> 24) & 0xffff);
	wr32(dev, tmp, MC_VRAM_LOCATION);

	/*
	 * maps pci bar 0: use the 256 kB block index of the gpu start
	 * address for vram. 1 GB of gpu address space.
	 */
	wr32(dev, drv_data->vram.range.start >> 8, HDP_NONSURFACE_BASE); 
	wr32(dev, (2 << 7) | (1 << 30), HDP_NONSURFACE_INFO);
	wr32(dev, 0x3fffffff, HDP_NONSURFACE_SZ);

	/* no agp aperture in physical addresses */
	wr32(dev, 0, MC_AGP_BASE);
	wr32(dev, 0x0fffffff, MC_AGP_TOP);
	wr32(dev, 0x0fffffff, MC_AGP_BOT);

	if (mc_wait_for_idle(dev))
		dev_warn(&dev->dev, "mc_program:start:"
					" wait for mc idle timed out\n");

	mc_dce_resume(dev, &mc_dce_save);

	/* no vga */
	wr32(dev, rr32(dev, VGA_RENDER_CTL) & VGA_VSTATUS_CTL_CLEAR,
								VGA_RENDER_CTL);
	return 0;
}
